﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;
using System.ComponentModel;
using System.Reflection;

namespace BookClub.Core {

    public class Command : DependencyObject, ICommand {

        public static readonly DependencyProperty TargetProperty =
            DependencyProperty.Register("Target", typeof(object), typeof(Command),
                                        new PropertyMetadata(OnViewModelPropertyChanged));

        private EventHandler _canExecuteChangedHandler;

        private object _target;
        private string _method;

        private bool _listeningForChanges;
        private PropertyInfo _canExecuteProperty;
        private MethodInfo _executeMethod;
        private bool _executeWithParameter;

        public Command() {
            Binding binding = new Binding();
            BindingOperations.SetBinding(this, TargetProperty, binding);
        }

        public string Method {
            get {
                return _method;
            }
            set {
                _method = value;
                Update();
            }
        }

        public object Target {
            get {
                return GetValue(TargetProperty);
            }
            set {
                SetValue(TargetProperty, value);
            }
        }

        private void OnTargetChanged(object newTarget) {
            if (DesignerProperties.IsInDesignTool) {
                return;
            }

            if (_listeningForChanges) {
                ((INotifyPropertyChanged)_target).PropertyChanged -= OnTargetPropertyChanged;
                _listeningForChanges = false;
            }

            _target = newTarget;
            if (_target != null) {
                INotifyPropertyChanged inpc = _target as INotifyPropertyChanged;
                if (inpc != null) {
                    inpc.PropertyChanged += OnTargetPropertyChanged;
                    _listeningForChanges = true;
                }
            }

            Update();
        }

        private void OnTargetPropertyChanged(object sender, PropertyChangedEventArgs e) {
            if (String.CompareOrdinal(e.PropertyName, "Can" + _method) == 0) {
                if (_canExecuteChangedHandler != null) {
                    _canExecuteChangedHandler(this, EventArgs.Empty);
                }
            }
        }

        private static void OnViewModelPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
            ((Command)o).OnTargetChanged(e.NewValue);
        }

        private void Update() {
            if (_target != null) {
                Type viewModelType = _target.GetType();
                _executeMethod = viewModelType.GetMethod(_method);
                _canExecuteProperty = viewModelType.GetProperty("Can" + _method);

                ParameterInfo[] parameters = _executeMethod.GetParameters();
                _executeWithParameter = ((parameters != null) && (parameters.Length == 1));
            }

            if (_canExecuteChangedHandler != null) {
                _canExecuteChangedHandler(this, EventArgs.Empty);
            }
        }


        #region Implementation of ICommand
        bool ICommand.CanExecute(object parameter) {
            if (DesignerProperties.IsInDesignTool == false) {
                if (_target == null) {
                    return false;
                }

                if (_canExecuteProperty != null) {
                    return (bool)_canExecuteProperty.GetValue(_target, null);
                }
            }

            return true;
        }

        event EventHandler ICommand.CanExecuteChanged {
            add {
                _canExecuteChangedHandler = (EventHandler)Delegate.Combine(_canExecuteChangedHandler, value);
            }
            remove {
                _canExecuteChangedHandler = (EventHandler)Delegate.Remove(_canExecuteChangedHandler, value);
            }
        }

        void ICommand.Execute(object parameter) {
            if ((_target != null) && (_executeMethod != null)) {
                if (_executeWithParameter) {
                    _executeMethod.Invoke(_target, new object[] { parameter });
                }
                else {
                    _executeMethod.Invoke(_target, null);
                }
            }
        }
        #endregion
    }
}
